#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include "list.h"
#include "zm32.h"
#include "zm32_ap.h"
#include "utils.h"
#include "debug.h"
#include "unidev.h"

#define SOI                         (0x63)
#define CMD_READ                    (0x03)
#define CMD_WRITE_SINGLE            (0x06)
#define CMD_REPORT                  (0x07)
#define CMD_WRITE_MULT              (0x10)



struct zm32 {
    pthread_mutex_t mutex;
    struct condition_val r_cond;
    struct condition_val w_cond;
    struct list_head recv;
} zm32;


struct zm32_info {
    unsigned long long macaddr;
    unsigned short nwkaddr;

    unsigned int addr;
    unsigned short reg;
    unsigned char cmd;
    unsigned char num;
    unsigned char data[255*2];
};


void zm32_init(void)
{
    pthread_mutex_init(&zm32.mutex, NULL);
    utils_cond_init(&zm32.r_cond);
    utils_cond_init(&zm32.w_cond);
    INIT_LIST_HEAD(&zm32.recv);
}


void zm32_finit(void)
{
    pthread_mutex_destroy(&zm32.mutex);
    utils_cond_destroy(&zm32.r_cond);
    utils_cond_destroy(&zm32.w_cond);
}
static int cmd_send(unsigned char cmd, unsigned long long mac,
                    unsigned short reg, unsigned char num, const void *data);


static unsigned short crc16(const unsigned char *Pushdata, unsigned short length)
{
    unsigned short Reg_CRC=0xffff;
    unsigned short i,j;
    for( i = 0; i<length; i++)
    {
        Reg_CRC^= (unsigned short)(*Pushdata++);
        for (j = 0; j<8; j++)
        {
            if (Reg_CRC & 0x0001)
                Reg_CRC=Reg_CRC>>1^0xA001;
            else
                Reg_CRC >>= 1;
        }
    }

    return Reg_CRC;
}



static int zm32_parse(const void *data, unsigned int len, struct zm32_info *info)
{
    const unsigned char *d = (const unsigned char *)data;
    unsigned int i = 0;
    unsigned short crc[2];

    if (!data || !info)
        return -1;
    if (len < 8)
        return -2;
    if (d[i++] != SOI)
        return -3;
    crc[0] = crc16(data, len - 2);
    crc[1] = d[len-2];
    crc[1] <<= 8U;
    crc[1] |= d[len-1];
    if (crc[0] != crc[1])
        return -4;
    memcpy(&info->addr, d+i, sizeof(info->addr));
    i += sizeof(info->addr);
    info->cmd = d[i++];
    switch (info->cmd) {
        case CMD_WRITE_SINGLE:
            memcpy(&info->reg, d+i, sizeof(info->reg));
            info->reg = ntohs(info->reg);
            i += sizeof(info->reg);
            memcpy(info->data, d+i, 2);
            break;

        case CMD_WRITE_MULT:
            memcpy(&info->reg, d+i, sizeof(info->reg));
            info->reg = ntohs(info->reg);
            i += sizeof(info->reg);
            info->num = d[i++];
            if (len - i > sizeof(crc[1]))
                memcpy(info->data, d+i, info->num*2);
            break;

        case CMD_READ:
            info->num = d[i++];
            info->reg = 0;
            memcpy(info->data, d+i, info->num*2);
            break;

        case CMD_REPORT:
            memcpy(&info->reg, d+i, sizeof(info->reg));
            info->reg = ntohs(info->reg);
            i += sizeof(info->reg);
            info->num = d[i++];
            if (len - i > sizeof(crc[1]))
                memcpy(info->data, d+i, info->num*2);
            break;

        default:
            return -5;
    }

    return 0;
}

static unsigned long long int hton(unsigned long long int val, unsigned int siz)
{
    if (siz == 2)
        return htons(val);
    else if (siz == 4)
        return htonl(val);
    else if (siz == 8)
        return htonll(val);
    else
        return val;
}

static unsigned long long int ntoh(unsigned long long int val, unsigned int siz)
{
    if (siz == 2)
        return ntohs(val);
    else if (siz == 4)
        return ntohl(val);
    else if (siz == 8)
        return ntohll(val);
    else
        return val;
}

int strton(const char *str, const struct attr_reg_map *arm, void *out)
{
    unsigned long long int tmp = 0;
    if (!str || !arm || !out)
        return UNIDEV_ERR_NULL;

    if (!strncmp(arm->attr.type, TYPE_INT_NAME, strlen(TYPE_INT_NAME))) {
        tmp = strtol(str, NULL, 10);
        tmp = hton(tmp, arm->regs.num);
        memcpy(out, &tmp, arm->regs.num);
    } else if (!strncmp(arm->attr.type, TYPE_BOOL_NAME, strlen(TYPE_BOOL_NAME))) {
        tmp = strtol(str, NULL, 10);
        tmp = hton(tmp, arm->regs.num);
        memcpy(out, &tmp, arm->regs.num);
    } else if (!strncmp(arm->attr.type, TYPE_FLOAT_NAME, strlen(TYPE_FLOAT_NAME))) {
        tmp = strtod(str, NULL);
        tmp = hton(tmp, arm->regs.num);
        memcpy(out, &tmp, arm->regs.num);
    } else if (!strncmp(arm->attr.type, TYPE_STR_NAME, strlen(TYPE_STR_NAME))) {
        memcpy(out, &tmp, arm->regs.num);
    } else if (!strncmp(arm->attr.type, TYPE_RAW_NAME, strlen(TYPE_RAW_NAME))) {
        if (base64_decode(str, strlen(str), out) != arm->regs.num)
            return UNIDEV_ERR_VALUE;
    } else {
        return UNIDEV_ERR_TYPE;
    }

    return UNIDEV_SUCCESS;
}

int ntostr(const void *data, const struct attr_reg_map *arm, char *str)
{
    unsigned long long int tmp = 0;

    if (!data || !arm || !str)
        return UNIDEV_ERR_NULL;

    if (!strncmp(arm->attr.type, TYPE_INT_NAME, strlen(TYPE_INT_NAME))) {
        memcpy(&tmp, data, arm->regs.num);
        tmp = ntoh(tmp, arm->regs.num);
        sprintf(str, "%lld", tmp);
    } else if (!strncmp(arm->attr.type, TYPE_BOOL_NAME, strlen(TYPE_BOOL_NAME))) {
        memcpy(&tmp, data, arm->regs.num);
        tmp = ntoh(tmp, arm->regs.num);
        sprintf(str, "%lld", tmp);
    } else if (!strncmp(arm->attr.type, TYPE_STR_NAME, strlen(TYPE_STR_NAME))) {
        strcpy(str, data);
    } else if (!strncmp(arm->attr.type, TYPE_FLOAT_NAME, strlen(TYPE_FLOAT_NAME))) {
        memcpy(&tmp, data, arm->regs.num);
        tmp = ntoh(tmp, arm->regs.num);
        sprintf(str, "%lf", (double)tmp);
    }  else {
        base64_encode(data, arm->regs.num, str);
    }

    return UNIDEV_SUCCESS;
}


extern struct dev_cb dev_cb;
static void report_proc(struct zm32_info *info)
{
    struct regs cur, next;
    cJSON *result = NULL, *it = NULL;
    char buf[128];
    unsigned char *data = NULL;
    cJSON *root = NULL;
    const struct attr_reg_map *arm = NULL;

    LOGD("report");
    cur.start = info->reg;
    cur.num = info->num;
    data = info->data;
    while (cur.num > 0)
    {
        arm = attr_reg_map_lookup(NULL, &cur);
        if (!arm)
        {
            goto out;
        }
        next.start = cur.start + 1;
        next.num = cur.num - arm->regs.num;
        result = cJSON_CreateObject();
        if (!result)
        {
            LOGI("no mem");
            goto out;
        }

        snprintf(buf, sizeof(buf), "%016llX", info->macaddr);
        if (cur.start == 0x3014) {
            it = cJSON_AddObjectToObject(result, "pdu");
            if (!it)
            {
                LOGI("no mem");
                goto out;
            }
            if (!cJSON_AddStringToObject(it, "uuid", buf))
            {
                LOGI("no mem");
                goto out;
            }
            if (!cJSON_AddStringToObject(it, "model id", (const char * const)data))
            {
                LOGI("no mem");
                goto out;
            }

            dev_cb.join(result);
            cJSON_Delete(result);
            result = NULL;
        } else if (cur.start == 0x1008) {
            it = cJSON_AddObjectToObject(result, "pdu");
            if (!it)
            {
                LOGI("no mem");
                goto out;
            }
            if (!cJSON_AddStringToObject(it, "uuid", buf))
            {
                LOGI("no mem");
                goto out;
            }
            dev_cb.quit(result);
            cJSON_Delete(result);
            result = NULL;
        } else {
            it = cJSON_AddObjectToObject(result, "pdu");
            if (!it)
            {
                LOGI("no mem");
                goto out;
            }
            if (!cJSON_AddStringToObject(it, "uuid", buf))
                goto out;
            if (!cJSON_AddNumberToObject(it, "attr id", arm->attr.id))
                goto out;
            if (!cJSON_AddNumberToObject(it, "channel", arm->attr.channel))
                goto out;
            if (ntostr(data, arm, buf) == UNIDEV_SUCCESS) {
                if (cJSON_AddStringToObject(it, "type", arm->attr.type) &&
                    cJSON_AddStringToObject(it, "value", buf))
                    dev_cb.attr(result);
            }
            cJSON_Delete(result);
            result = NULL;
        }
        data += arm->regs.num;
        cur = next;
    }
    cmd_send(CMD_REPORT, info->macaddr, info->reg, info->num, NULL);

out:
    if (result) {
        cJSON_Delete(result);
        result = NULL;
    }
    if (root) {
        cJSON_Delete(root);
        root = NULL;
    }
}


void zm32_msg_proc(void *msg)
{
    struct frame_rtx *m = (struct frame_rtx *)msg;
    struct zm32_info *info = NULL;

    info = (struct zm32_info *)malloc(sizeof(*info));
    if (!info)
        return;

    info->macaddr = m->macaddr;
    info->nwkaddr = m->nwkaddr;
    if (!zm32_parse(m->payload, m->length, info)) {
        switch (info->cmd) {
            case CMD_WRITE_SINGLE:
                if (zm32.w_cond.val) {
                    free(zm32.w_cond.val);
                    zm32.w_cond.val = NULL;
                }
                utils_cond_signal(&zm32.w_cond, info);
                break;

            case CMD_REPORT:
                report_proc(info);
                break;

            case CMD_WRITE_MULT:
                if (m->length == 11) {
                    if (zm32.w_cond.val) {
                        free(zm32.w_cond.val);
                        zm32.w_cond.val = NULL;
                    }
                    utils_cond_signal(&zm32.w_cond, info);
                } else {
                    report_proc(info);
                }
                break;

            case CMD_READ:
                if (zm32.r_cond.val) {
                    free(zm32.r_cond.val);
                    zm32.r_cond.val = NULL;
                }
                utils_cond_signal(&zm32.r_cond, info);
                break;
            default:
                break;
        }
    }
}


static int cmd_send(unsigned char cmd, unsigned long long int mac,
        unsigned short reg, unsigned char num, const void *data)
{
    unsigned char buf[255+9];
    unsigned int i = 0;
    unsigned short crc = 0;
    unsigned int addr = 0;
    int ret;
    //unsigned short netaddr = 0;

    if (!num)
        return -1;

    buf[i++] = SOI;
    buf[i++] = addr >> 24U;
    buf[i++] = addr >> 16U;
    buf[i++] = addr >> 8U;
    buf[i++] = addr;
    buf[i++] = cmd;
    buf[i++] = reg >> 8U;
    buf[i++] = reg;
    buf[i++] = num;
    if (data) {
        memcpy(buf+i, data, num);
        i += num;
    }
    crc = crc16(buf, i);
    buf[i++] = crc >> 8U;
    buf[i++] = crc;

    pthread_mutex_lock(&zm32.mutex);
#if 0
    if (mac == BROADCAST_MAC_ALL) {
        netaddr = 0xFFFF;
        ret = zm32_spec_tx_frame(&netaddr, sizeof(netaddr), buf, i);
    } else if (mac == BROADCAST_MAC_FFD || mac == BROADCAST_MAC_NOSLEEP) {
        netaddr = 0xFFFB;
        ret = zm32_spec_tx_frame(&netaddr, sizeof(netaddr), buf, i);
    } else
#endif
        ret = zm32_spec_tx_frame(&mac, sizeof(mac), buf, i);
    pthread_mutex_unlock(&zm32.mutex);

    return ret;
}



int zm32_read(unsigned long long mac, unsigned short reg,
              unsigned char num, void *data)
{
    int ret;
    struct zm32_info *info;

    ret = cmd_send(CMD_READ, mac, reg, num, NULL);
    if (ret)
    {
        LOGI("cmd_send failed. ret = %d", ret);
        goto out;
    }

    if (EN_UTILS_OK != utils_cond_wait_until(&zm32.r_cond, 3000)) {
        ret = -2;
        goto out;
    }
    info = (struct zm32_info *)zm32.r_cond.val;
    if (info->num == num) {
        memcpy(data, info->data, num);
        ret = 0;
    } else {
        ret = -3;
    }

    free(zm32.r_cond.val);
    zm32.r_cond.val = NULL;

out:

    return ret;
}


int zm32_write(unsigned long long int mac, unsigned short reg,
               unsigned char num, const void *data)
{
    int ret = 0;

    ret = cmd_send(CMD_WRITE_MULT, mac, reg, num, data);
    if (ret)
    {
        LOGI("cmd_send failed. ret = %d", ret);
        goto out;
    }
    if (mac == BROADCAST_MAC_ALL || mac == BROADCAST_MAC_NOSLEEP || mac == BROADCAST_MAC_FFD) {
        ret = 0;
        goto out;
    }

    if (EN_UTILS_OK != utils_cond_wait_until(&zm32.w_cond, 3000)) {
        ret = -2;
        goto out;
    }
    ret = 0;

    if (zm32.w_cond.val) {
        free(zm32.w_cond.val);
        zm32.w_cond.val = NULL;
    }

out:

    return ret;
}